#ifdef ONLINE
#include "Network.h"

#define WINSOCK_VER MAKEWORD(2, 2)
#define RECVBUFFER_SIZE 8192

SOCKET _socket = NULL;
queue<Network::Packet*> writeQueue;
char *recvBuffer = new char[RECVBUFFER_SIZE];

void Network::ignite() {
	Endian::init();
	std::cout<<"System is"<<(Endian::endian() == Endian::Kind::BIG ? " BIG ENDIAN" : " LITTLE ENDIAN")<<std::endl;
#ifdef ONLINE
	if (!Network::initialize()) {
		std::cerr<<"Failed to initialize networking, system returned ";
#ifdef _WIN32
		std::cerr<<NetworkError;
#else
		std::cerr<<"\"The initialize call should not fail on a POSIX enabled system.\"";
#endif
		std::cerr<<std::endl;
	}
	string ip;
	unsigned short port;
	std::cout << "IP Address:" << endl;
	std::cin >> ip;
	std::cout << "Port:"<< endl;
	std::cin >> port;
	Network::connectSocket(ip, port);
	SDL_CreateThread(Network::runSelector, NULL);
#endif
}

/**
 * Initializes the network. On Windows, this calls WSAStartup from Ws2_32.dll
 */
bool Network::initialize() {
#ifdef _WIN32
	WSADATA wsaData;
	int bRet = WSAStartup(WINSOCK_VER, &wsaData);
	return (bRet == 0);
#else
	return true;
#endif
}

/**
 * Shuts down the network. Further calls to the network will not succeed unless Network::initialize() is invoked again.
 * On windows, this calls WSACleanup from WS2_32.dll
 */
void Network::shutdown() {
	Network::closeSocket();
#ifdef _WIN32
	WSACleanup();
#endif
}

/**
 * Connects the network to a remote location.
 * @param ip IP Address
 * @param Port port number
 * @throws WSAGetLastError if an error occurs
 */
void Network::connectSocket(std::string ip, unsigned short port) {
	if (_socket) {
		throw 1;
	}
	_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (!_socket) {
		throw 2; // TODO: errorcodes (exceptions)
	}
	sockaddr_in addr; 
	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = InternetAddress(ip.c_str());
	addr.sin_port = HostToNetworkShort(port);
	if (connect(_socket, (sockaddr*) &addr, sizeof(addr)) != 0) {
#ifdef _WIN32
		std::cerr<<"Winsock error: "<<NetworkError;
		throw NetworkError;
#else
		std::cerr<<"Unix sockets error: "<<NetworkError;
		throw NetworkError;
#endif
	}
	// set socket to non blocking mode
#ifdef _WIN32
	u_long blockMode = 0;
	SocketIOControl(_socket, FIONBIO, &blockMode);
#else
	SocketIOControl(_socket, F_SETFL, O_NONBLOCK);
#endif
}

/**
 * Closes the network socket.
 * @return true on successful closure, false otherwise
 */
bool Network::closeSocket() {
	if (!_socket) {
		return false;
	}
	bool bRet = CloseSocket(_socket) == 0;
	_socket = NULL;
	return bRet;
}

/**
 * Runs the network selector.
 * @return -1 on error, or 0 on successful termination.
 */
int Network::runSelector(void *unused) {
	struct timeval tv;
	while (1 == 1 && 2 == 2 && 3 == 3 && 4 == 4 && 5 == 5 && 6 == 6) {
		tv.tv_sec = 1;
		fd_set readfd, writefd;
		FD_ZERO(&readfd);
		FD_ZERO(&writefd);
		FD_SET(_socket, &readfd);
		FD_SET(_socket, &writefd);
		int ready = select(_socket + 1, &readfd, &writefd, (fd_set*) 0, &tv);
		if (ready < 0) {
			std::cerr<<"Error in select()"<<std::endl;
			return -1;
		} else if (ready == 0) {
			std::cout<<"No data (Select timeout)"<<std::endl;
		} else {
			if (FD_ISSET(_socket, &readfd)) {
				// ready for reading
				std::cout<<"Network - ready for reading"<<std::endl;
				int readBytes = recv(_socket, recvBuffer, RECVBUFFER_SIZE, 0);
				if (readBytes < 0) {
					std::cerr<<"Network - recieve error: "<<NetworkError<<std::endl;
					return -1;
				} else if (readBytes == 0) {
					std::cout<<"Network - connection closed successfully."<<std::endl;
					return 0;
				} else {
					std::cout<<"Read "<<readBytes<<" bytes successfully."<<std::endl;
				}
			}
			if (FD_ISSET(_socket, &writefd)) {
				// ready for writing
				if (writeQueue.size() > 0) {
					std::cout<<"Network - ready for writing"<<std::endl;
					while (1) {
						Network::PACKET packet = writeQueue.front();
						if (packet->consumed()) {
							writeQueue.pop();
							continue;
						}
						char *data = packet->packet();
						int packetsize = packet->position() - packet->pointer();
						char *sendData = new char[packetsize];
						for (auto i = 0; i < packetsize; i++) {
							sendData[i] = data[packet->pointer() + i];
						}
						int sentBytes = send(_socket, sendData, packetsize, 0);
						if (sentBytes < 0) {
							std::cerr<<"Send error: "<<NetworkError<<std::endl;
							return -1;
						}
						delete sendData;
						packet->incrementPointer(sentBytes);
						std::cout<<"Sent "<<sentBytes<<" bytes successfully."<<std::endl;
					}
				}
			}
		}
	}
	return 0;
}

/////////////////////////////////////////
/////////// PACKETS STUFF ///////////////
/////////////////////////////////////////

Network::Packet::Packet(int capacity) : _capacity(capacity), _position(0) {
	data = new char[capacity];
}

Network::Packet::~Packet() {
	if (data) {
		delete data;
		data = NULL;
	}
}

void Network::Packet::expandBuffer(int additionalBytes) {
	if (_position + additionalBytes > _capacity) {
		char *tempData = new char[_position + additionalBytes];
		memcpy(tempData, data, _position);
		delete data;
		data = tempData;
		_capacity = _position + additionalBytes;
	}
}

void Network::Packet::writeByte(char val) {
	expandBuffer(1);
	data[_position++] = val;
}

void Network::Packet::writeShort(short val) {
	expandBuffer(2);
	val = Endian::littleShort(val);
	(*(short*)(data + _position)) = val;
	_position += 2;
}

void Network::Packet::writeInt(int val) {
	expandBuffer(4);
	val = Endian::littleLong(val);
	(*(int*)(data + _position)) = val;
	_position += 4;
}

void Network::Packet::writeLong(__int64 val) {
	expandBuffer(8);
	(*(__int64*)(data + _position)) = val;
	_position += 8;
}

void Network::Packet::writeAsciiString(std::string &val) {
	writeShort(val.length());
	expandBuffer(val.length());
	for (auto i = 0; i < val.length(); i++) {
		writeByte(val[i]);
	}
}

void Network::writePacket(Packet *p) {
	writeQueue.push(p);
}

bool Network::Packet::consumed() {
	return _position == _pointer;
}

char *Network::Packet::packet() {
	return data;
}

void Network::Packet::incrementPointer(unsigned int v) {
	_pointer += v;
}

#endif